/*
Copyright (C) 1994-1995 Apogee Software, Ltd.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
/**********************************************************************
   module: MULTIVOC.C

   author: James R. Dose
   date:   December 20, 1993

   Routines to provide multichannel digitized sound playback for
   Sound Blaster compatible sound cards.

   (c) Copyright 1993 James R. Dose.  All Rights Reserved.
**********************************************************************/

#include <stdlib.h>
#include "dpmi.h"
#include "usrhooks.h"
#include "interrup.h"
#include "dpmi.h"
#include "ll_man.h"
#include "sndcards.h"
#include "blaster.h"
#include "sndsrc.h"
#include "pas16.h"
#include "pitch.h"
#include "multivoc.h"
#include "_multivc.h"

static signed char MV_VolumeTable[ sizeof( VOLUME_TABLE_16BIT ) ];

static VOLUME_TABLE_8BIT  *MV_8BitVolumeTable  = &MV_VolumeTable[ 0 ];
static VOLUME_TABLE_16BIT *MV_16BitVolumeTable = &MV_VolumeTable[ 0 ];

static Pan MV_PanTable[ MV_NumPanPositions ][ MV_MaxVolume + 1 ];

static int MV_Installed   = FALSE;
static int MV_SoundCard   = SoundBlaster;
static int MV_TotalVolume = MV_MaxTotalVolume;
static int MV_MaxVoices   = 1;

static int MV_BufferSize = MixBufferSize;
static int MV_SampleSize = 1;
static int MV_NumberOfBuffers = NumberOfBuffers;

static int MV_MixMode    = MONO_8BIT;
static int MV_Silence    = SILENCE_8BIT;
static int MV_SwapLeftRight = FALSE;

static int MV_RequestedMixRate;
static int MV_MixRate;

static int   MV_BufferDescriptor;
static char *MV_MixBuffer[ NumberOfBuffers ];

static VoiceNode *MV_Voices = NULL;
//static VoiceNode MV_Voices[ 16 ];

static volatile VList VoiceList = { NULL, NULL };
static volatile VList VoicePool = { NULL, NULL };

static int MV_MixPage      = 0;
static int MV_PlayPage     = 0;
static int MV_VoiceHandle  = MV_MinVoiceHandle;

static void ( *MV_CallBackFunc )( unsigned long ) = NULL;

//char HarshClipTable[ 8 * 256 ];
//unsigned short HarshClipTable16[ 8 * 256 * 16 ];

char           *HarshClipTable;
unsigned short *HarshClipTable16;
         short *HarshClipTable16s;

int MV_ErrorCode = MV_Ok;

#define MV_SetErrorCode( status ) \
   MV_ErrorCode   = ( status );


/*---------------------------------------------------------------------
   Function: MV_ErrorString

   Returns a pointer to the error message associated with an error
   number.  A -1 returns a pointer the current error.
---------------------------------------------------------------------*/

char *MV_ErrorString
   (
   int ErrorNumber
   )

   {
   char *ErrorString;

   switch( ErrorNumber )
      {
      case MV_Warning :
      case MV_Error :
         ErrorString = MV_ErrorString( MV_ErrorCode );
         break;

      case MV_Ok :
         ErrorString = "Multivoc ok.";
         break;

      case MV_UnsupportedCard :
         ErrorString = "Selected sound card is not supported by Multivoc.";
         break;

      case MV_NotInstalled :
         ErrorString = "Multivoc not installed.";
         break;

      case MV_NoVoices :
         ErrorString = "No free voices available to Multivoc.";
         break;

      case MV_NoMem :
         ErrorString = "Out of memory in Multivoc.";
         break;

      case MV_VoiceNotFound :
         ErrorString = "No voice with matching handle found.";
         break;

      case MV_BlasterError :
         ErrorString = BLASTER_ErrorString( BLASTER_Error );
         break;

      case MV_PasError :
         ErrorString = PAS_ErrorString( PAS_Error );
         break;

      #ifndef SOUNDSOURCE_OFF
      case MV_SoundSourceError :
         ErrorString = SS_ErrorString( SS_Error );
         break;
      #endif

      case MV_DPMI_Error :
         ErrorString = "DPMI Error in Multivoc.";
         break;

      default :
         ErrorString = "Unknown Multivoc error code.";
         break;
      }

   return( ErrorString );
   }


/**********************************************************************

   Memory locked functions:

**********************************************************************/


#define MV_LockStart MV_Mix8bitMono


/*---------------------------------------------------------------------
   Function: MV_Mix8bitMono

   Mixes the sound into the buffer as an 8 bit mono sample.
---------------------------------------------------------------------*/

void MV_Mix8bitMono
   (
   VoiceNode *voice,
   int        buffer
   )

   {
//   MONO8        *to;
   char         *to;
   VOLUME8      *VolumeTable;
   char         *start;
   char         *from;
   unsigned long position;
   int           length;
   unsigned long rate;

   int samp;

   to = ( MONO8 * )MV_MixBuffer[ buffer ];

   VolumeTable = ( VOLUME8 * )&( *MV_8BitVolumeTable )[ voice->Volume ];

   start    = voice->sound;
   rate     = voice->RateScale;
   position = voice->position;

   length   = MixBufferSize;

   // Add this voice to the mix
   while( length > 0 )
      {
      if ( position >= voice->length )
         {
         voice->position = position;
         MV_GetNextVOCBlock( voice );
         if ( voice->Playing )
            {
            start    = voice->sound;
            rate     = voice->RateScale;
            position = voice->position;
            }
         else
            {
            break;
            }
         }

//      from = start + ( position >> 16 );
//      *to += ( *VolumeTable )[ *from ];
      from = start + ( position >> 16 );
      samp = ( ( int )*to ) + ( *VolumeTable )[ *from ];
      *to  = HarshClipTable[ 4 * 256 + samp - 0x80 ];

      to++;
      position += rate;

      length--;
      }

   voice->position = position;
   }


/*---------------------------------------------------------------------
   Function: MV_Mix8bitStereo

   Mixes the sound into the buffer as an 8 bit stereo sample.
---------------------------------------------------------------------*/

void MV_Mix8bitStereo
   (
   VoiceNode *voice,
   int        buffer
   )

   {
   STEREO8      *to;
   VOLUME8      *LeftVolumeTable;
   VOLUME8      *RightVolumeTable;
   char         *start;
   unsigned long position;
   int           length;
//   int           sample;
   unsigned long rate;

   char         *from;
   int samp;

   to = ( STEREO8 * )MV_MixBuffer[ buffer ];

   LeftVolumeTable  = ( VOLUME8 * )&( *MV_8BitVolumeTable )[ voice->LeftVolume ];
   RightVolumeTable = ( VOLUME8 * )&( *MV_8BitVolumeTable )[ voice->RightVolume ];

   start    = voice->sound;
   rate     = voice->RateScale;
   position = voice->position;

   length   = MixBufferSize;

   // Add this voice to the mix
   while( length > 0 )
      {
      if ( position >= voice->length )
         {
         voice->position = position;
         MV_GetNextVOCBlock( voice );
         if ( voice->Playing )
            {
            start    = voice->sound;
            rate     = voice->RateScale;
            position = voice->position;
            }
         else
            {
            break;
            }
         }
//      sample = ( int )*( start + ( position >> 16 ) );
//      to->left  += ( *LeftVolumeTable )[ sample ];
//      to->right += ( *RightVolumeTable )[ sample ];

      from = start + ( position >> 16 );
      samp = ( ( int )to->left ) + ( *LeftVolumeTable )[ *from ];
      to->left = HarshClipTable[ 4 * 256 + samp - 0x80 ];
      samp = ( ( int )to->right ) + ( *RightVolumeTable )[ *from ];
      to->right = HarshClipTable[ 4 * 256 + samp - 0x80 ];

      to++;
      position += rate;
      length--;
      }

   voice->position = position;
   }


/*---------------------------------------------------------------------
   Function: MV_Mix16bitUnsignedMono

   Mixes the sound into the buffer as an 16 bit mono sample.
---------------------------------------------------------------------*/

void MV_Mix16bitUnsignedMono
   (
   VoiceNode *voice,
   int        buffer
   )

   {
//   MONO16       *to;
   unsigned short *to;
   VOLUME16     *VolumeTable;
   char         *start;
   char         *from;
   unsigned long position;
   int           length;
   unsigned long rate;

   int samp;

//   to = ( MONO16 * )MV_MixBuffer[ buffer ];
   to = ( unsigned short * )MV_MixBuffer[ buffer ];

   VolumeTable = ( VOLUME16 * )&( *MV_16BitVolumeTable )[ voice->Volume ];

   start    = voice->sound;
   rate     = voice->RateScale;
   position = voice->position;
   length   = MixBufferSize;

   // Add this voice to the mix
   while( length > 0 )
      {
      if ( position >= voice->length )
         {
         voice->position = position;
         MV_GetNextVOCBlock( voice );
         if ( voice->Playing )
            {
            start    = voice->sound;
            rate     = voice->RateScale;
            position = voice->position;
            }
         else
            {
            break;
            }
         }
//      from = start + ( position >> 16 );
//      *to += ( *VolumeTable )[ *from ];

      from = start + ( position >> 16 );
      samp = ( ( *to - 0x8000 ) >> 4 ) +
         ( *VolumeTable )[ *from ];
      *to = HarshClipTable16[ 4 * 256 * 16 + samp ];// << 4;
      to++;
      position += rate;
      length--;
      }

   voice->position = position;
   }


/*---------------------------------------------------------------------
   Function: MV_Mix16bitUnsignedStereo

   Mixes the sound into the buffer as an 16 bit stereo sample.
---------------------------------------------------------------------*/

void MV_Mix16bitUnsignedStereo
   (
   VoiceNode *voice,
   int        buffer
   )

   {
   STEREO16     *to;
   VOLUME16     *LeftVolumeTable;
   VOLUME16     *RightVolumeTable;
   char         *start;
   unsigned long position;
   int           length;
//   int           sample;
   unsigned long rate;

   char         *from;
   int samp;

   to = ( STEREO16 * )MV_MixBuffer[ buffer ];

   LeftVolumeTable  = ( VOLUME16 * )&( *MV_16BitVolumeTable )[ voice->LeftVolume ];
   RightVolumeTable = ( VOLUME16 * )&( *MV_16BitVolumeTable )[ voice->RightVolume ];

   start    = voice->sound;
   rate     = voice->RateScale;
   position = voice->position;

   length   = MixBufferSize;

   // Add this voice to the mix
   while( length > 0 )
      {
      if ( position >= voice->length )
         {
         voice->position = position;
         MV_GetNextVOCBlock( voice );
         if ( voice->Playing )
            {
            start    = voice->sound;
            rate     = voice->RateScale;
            position = voice->position;
            }
         else
            {
            break;
            }
         }
//      sample = ( int )*( start + ( position >> 16 ) );
//      to->left  += ( *LeftVolumeTable )[ sample ];
//      to->right += ( *RightVolumeTable )[ sample ];

      from = start + ( position >> 16 );
      samp = ( ( to->left - 0x8000 ) >> 4 ) +
         ( *LeftVolumeTable )[ *from ];
      to->left = HarshClipTable16[ 4 * 256 * 16 + samp ];// << 4;
//      to->left = ( length & 127 ) << 8;

      samp = ( ( to->right - 0x8000 ) >> 4 ) +
         ( *RightVolumeTable )[ *from ];
      to->right = HarshClipTable16[ 4 * 256 * 16 + samp ];// << 4;
//      to->right = ( length & 127 ) << 8;

      to++;
      position += rate;

      length--;
      }

   voice->position = position;
   }


/*---------------------------------------------------------------------
   Function: MV_PrepareBuffer

   Initializes the current buffer and mixes the currently active
   voices.
---------------------------------------------------------------------*/

void MV_PrepareBuffer
   (
   int page
   )

   {
   VoiceNode   *voice;

   // Initialize buffer
   ClearBuffer_DW( MV_MixBuffer[ page ], MV_Silence, MV_BufferSize >> 2 );

   voice = VoiceList.start;
   while( voice != NULL )
      {
      switch( MV_MixMode )
         {
         case MONO_8BIT :
            MV_Mix8bitMono( voice, page );
            break;

         case STEREO_8BIT :
            MV_Mix8bitStereo( voice, page );
            break;

         case MONO_16BIT :
            MV_Mix16bitUnsignedMono( voice, page );
            break;

         case STEREO_16BIT :
            MV_Mix16bitUnsignedStereo( voice, page );
            break;
         }

      if ( voice->Playing )
         {
         voice->Active[ page ] = TRUE;
         }
      else
         {
         voice->Active[ page ] = FALSE;
         }

      voice = voice->next;
      }
   }


/*---------------------------------------------------------------------
   Function: MV_DeleteDeadVoices

   Removes any voices that have finished playing.
---------------------------------------------------------------------*/

void MV_DeleteDeadVoices
   (
   int page
   )

   {
   VoiceNode   *voice;
   VoiceNode   *next;
   unsigned    flags;

   flags = DisableInterrupts();

   voice = VoiceList.start;
   while( voice != NULL )
      {
      next = voice->next;

      // Is this voice done?
      if ( !voice->Active[ page ] )
         {
         // Yes, move it from the play list into the free list
         LL_Remove( VoiceNode, &VoiceList, voice );
         LL_AddToTail( VoiceNode, &VoicePool, voice );

         if ( MV_CallBackFunc )
            {
            MV_CallBackFunc( voice->callbackval );
            }
         }

      voice = next;
      }

   RestoreInterrupts( flags );
   }


/*---------------------------------------------------------------------
   Function: MV_ServiceVoc

   Starts playback of the waiting buffer and mixes the next one.
---------------------------------------------------------------------*/

void MV_ServiceVoc
   (
   void
   )

   {
   int ErasePage;

   ErasePage = MV_PlayPage;

   // Delete any voices that are done playing
   MV_DeleteDeadVoices( ErasePage );

   // Set which buffer is currently being played.
   MV_PlayPage = MV_MixPage;

   // Toggle which buffer we'll mix next
   MV_MixPage++;
   if ( MV_MixPage >= MV_NumberOfBuffers )
      {
      MV_MixPage = 0;
      }

   // Play any waiting voices
   MV_PrepareBuffer( MV_MixPage );
   }


/*---------------------------------------------------------------------
   Function: MV_GetVoice

   Locates the voice with the specified handle.
---------------------------------------------------------------------*/

VoiceNode *MV_GetVoice
   (
   int handle
   )

   {
   VoiceNode *voice;
   unsigned  flags;

   flags = DisableInterrupts();

   voice = VoiceList.start;

   while( voice != NULL )
      {
      if ( handle == voice->handle )
         {
         break;
         }

      voice = voice->next;
      }

   RestoreInterrupts( flags );

   if ( voice == NULL )
      {
      MV_SetErrorCode( MV_VoiceNotFound );
      }

   return( voice );
   }


/*---------------------------------------------------------------------
   Function: MV_VoicePlaying

   Checks if the voice associated with the specified handle is
   playing.
---------------------------------------------------------------------*/

int MV_VoicePlaying
   (
   int handle
   )

   {
   VoiceNode   *voice;

   if ( !MV_Installed )
      {
      MV_SetErrorCode( MV_NotInstalled );
      return( FALSE );
      }

   voice = MV_GetVoice( handle );

   if ( voice == NULL )
      {
      return( FALSE );
      }

   return( TRUE );
   }


/*---------------------------------------------------------------------
   Function: MV_KillAllVoices

   Stops output of all currently active voices.
---------------------------------------------------------------------*/

int MV_KillAllVoices
   (
   void
   )

   {
   if ( !MV_Installed )
      {
      MV_SetErrorCode( MV_NotInstalled );
      return( MV_Error );
      }

   // Remove all the voices from the list
   while( VoiceList.start != NULL )
      {
      MV_Kill( VoiceList.start->handle );
      }

   return( MV_Ok );
   }


/*---------------------------------------------------------------------
   Function: MV_Kill

   Stops output of the voice associated with the specified handle.
---------------------------------------------------------------------*/

int MV_Kill
   (
   int handle
   )

   {
   VoiceNode *voice;
   unsigned  flags;
   unsigned  long callbackval;

   if ( !MV_Installed )
      {
      MV_SetErrorCode( MV_NotInstalled );
      return( MV_Error );
      }

   flags = DisableInterrupts();

   voice = MV_GetVoice( handle );
   if ( voice == NULL )
      {
      RestoreInterrupts( flags );
      MV_SetErrorCode( MV_VoiceNotFound );
      return( MV_Error );
      }

   callbackval = voice->callbackval;

   // move the voice from the play list to the free list
   LL_Remove( VoiceNode, &VoiceList, voice );
   LL_AddToTail( VoiceNode, &VoicePool, voice );

   RestoreInterrupts( flags );

   if ( MV_CallBackFunc )
      {
      MV_CallBackFunc( callbackval );
      }

   MV_SetErrorCode( MV_Ok );
   return( MV_Ok );
   }


/*---------------------------------------------------------------------
   Function: MV_VoicesPlaying

   Determines the number of currently active voices.
---------------------------------------------------------------------*/

int MV_VoicesPlaying
   (
   void
   )

   {
   VoiceNode   *voice;
   int         NumVoices = 0;
   unsigned    flags;

   if ( !MV_Installed )
      {
      MV_SetErrorCode( MV_NotInstalled );
      return( 0 );
      }

   flags = DisableInterrupts();

   voice = VoiceList.start;
   while( voice != NULL )
      {
      NumVoices++;
      voice = voice->next;
      }

   RestoreInterrupts( flags );

   return( NumVoices );
   }


/*---------------------------------------------------------------------
   Function: MV_AllocVoice

   Retrieve an inactive or lower priority voice for output.
---------------------------------------------------------------------*/

VoiceNode *MV_AllocVoice
   (
   int priority
   )

   {
   VoiceNode   *voice;
   VoiceNode   *node;
   unsigned    flags;

   flags = DisableInterrupts();

   // Check if we have any free voices
   if ( VoicePool.start == NULL )
      {
      // check if we have a higher priority than a voice that is playing.
      node = VoiceList.start;
      voice = node;
      while( node != NULL )
         {
         if ( node->priority < voice->priority )
            {
            voice = node;
            }

         node = node->next;
         }

      if ( priority >= voice->priority )
         {
         MV_Kill( voice->handle );
         }
      }

   // Check if any voices are in the voice pool
   if ( VoicePool.start == NULL )
      {
      // No free voices
      RestoreInterrupts( flags );
      return( NULL );
      }

   voice = VoicePool.start;
   LL_Remove( VoiceNode, &VoicePool, voice );
   RestoreInterrupts( flags );

   // Find a free voice handle
   do
      {
      MV_VoiceHandle++;
      if ( MV_VoiceHandle < MV_MinVoiceHandle )
         {
         MV_VoiceHandle = MV_MinVoiceHandle;
         }
      }
   while( MV_VoicePlaying( MV_VoiceHandle ) );

   voice->handle = MV_VoiceHandle;

   return( voice );
   }


/*---------------------------------------------------------------------
   Function: MV_SetPitch

   Sets the pitch for the voice associated with the specified handle.
---------------------------------------------------------------------*/

int MV_SetPitch
   (
   int handle,
   int pitchoffset
   )

   {
   VoiceNode *voice;

   if ( !MV_Installed )
      {
      MV_SetErrorCode( MV_NotInstalled );
      return( MV_Error );
      }

   voice = MV_GetVoice( handle );
   if ( voice == NULL )
      {
      MV_SetErrorCode( MV_VoiceNotFound );
      return( MV_Error );
      }

   voice->PitchScale  = PITCH_GetScale( pitchoffset );
   voice->RateScale   = ( voice->SamplingRate *
      voice->PitchScale ) / MV_MixRate;

   MV_SetErrorCode( MV_Ok );
   return( MV_Ok );
   }


/*---------------------------------------------------------------------
   Function: MV_SetPan

   Sets the stereo and mono volume level of the voice associated
   with the specified handle.
---------------------------------------------------------------------*/

int MV_SetPan
   (
   int handle,
   int vol,
   int left,
   int right
   )

   {
   VoiceNode *voice;

   if ( !MV_Installed )
      {
      MV_SetErrorCode( MV_NotInstalled );
      return( MV_Error );
      }

   voice = MV_GetVoice( handle );
   if ( voice == NULL )
      {
      MV_SetErrorCode( MV_VoiceNotFound );
      return( MV_Warning );
      }

   voice->Volume      = MIX_VOLUME( vol );

   if ( MV_SwapLeftRight )
      {
      // SBPro uses reversed panning
      voice->LeftVolume  = MIX_VOLUME( right );
      voice->RightVolume = MIX_VOLUME( left );
      }
   else
      {
      voice->LeftVolume  = MIX_VOLUME( left );
      voice->RightVolume = MIX_VOLUME( right );
      }

   MV_SetErrorCode( MV_Ok );
   return( MV_Ok );
   }


/*---------------------------------------------------------------------
   Function: MV_Pan3D

   Set the angle and distance from the listener of the voice associated
   with the specified handle.
---------------------------------------------------------------------*/

int MV_Pan3D
   (
   int handle,
   int angle,
   int distance
   )

   {
   int left;
   int right;
   int mid;
   int volume;
   int status;

   if ( distance < 0 )
      {
      distance  = -distance;
      angle    += MV_NumPanPositions / 2;
      }

   volume = MIX_VOLUME( distance );

   // Ensure angle is within 0 - 31
   angle &= MV_MaxPanPosition;

   left  = MV_PanTable[ angle ][ volume ].left;
   right = MV_PanTable[ angle ][ volume ].right;
   mid   = max( 0, 255 - distance );

   status = MV_SetPan( handle, mid, left, right );

   return( status );
   }


/*---------------------------------------------------------------------
   Function: MV_SetMixMode

   Prepares Multivoc to play stereo of mono digitized sounds.
---------------------------------------------------------------------*/

int MV_SetMixMode
   (
   int mode
   )

   {
   if ( !MV_Installed )
      {
      MV_SetErrorCode( MV_NotInstalled );
      return( MV_Error );
      }

   switch( MV_SoundCard )
      {
      case SoundBlaster :
      case Awe32 :
         MV_MixMode = BLASTER_SetMixMode( mode );
         break;

      case ProAudioSpectrum :
      case SoundMan16 :
         MV_MixMode = PAS_SetMixMode( mode );
         break;

      #ifndef SOUNDSOURCE_OFF
      case SoundSource :
      case TandySoundSource :
         MV_MixMode = SS_SetMixMode( mode );
         break;
      #endif
      }

   MV_SampleSize = sizeof( MONO8 );
   MV_Silence = SILENCE_8BIT;

   switch( MV_MixMode )
      {
      case MONO_8BIT :
         MV_SampleSize = sizeof( MONO8 );
         MV_Silence = SILENCE_8BIT;
         break;

      case STEREO_8BIT :
         MV_SampleSize = sizeof( STEREO8 );
         MV_Silence = SILENCE_8BIT;
         break;

      case MONO_16BIT :
         MV_SampleSize = sizeof( MONO16 );
//         if ( ( MV_SoundCard == ProAudioSpectrum ) ||
//            ( MV_SoundCard == SoundMan16 ) )
//            {
//            MV_Silence = SILENCE_16BIT_PAS;
//            }
//         else
//            {
            MV_Silence = SILENCE_16BIT;
//            }
         break;

      case STEREO_16BIT :
         MV_SampleSize = sizeof( STEREO16 );
//         if ( ( MV_SoundCard == ProAudioSpectrum ) ||
//            ( MV_SoundCard == SoundMan16 ) )
//            {
//            MV_Silence = SILENCE_16BIT_PAS;
//            }
//         else
//            {
            MV_Silence = SILENCE_16BIT;
//            }
         break;
      }

   MV_BufferSize = MixBufferSize * MV_SampleSize;

   MV_NumberOfBuffers = TotalBufferSize / MV_BufferSize;

   return( MV_Ok );
   }


/*---------------------------------------------------------------------
   Function: MV_StartPlayback

   Starts the sound playback engine.
---------------------------------------------------------------------*/

int MV_StartPlayback
   (
   void
   )

   {
   int status;

   // Initialize the buffers
   ClearBuffer_DW( MV_MixBuffer[ 0 ], MV_Silence, TotalBufferSize >> 2 );

   // Set the mix buffer variables
   MV_PlayPage = 0;
   MV_MixPage  = 1;

   // Start playback
   switch( MV_SoundCard )
      {
      case SoundBlaster :
      case Awe32 :
         status = BLASTER_BeginBufferedPlayback( MV_MixBuffer[ 0 ],
            TotalBufferSize, MV_NumberOfBuffers,
            MV_RequestedMixRate, MV_MixMode, MV_ServiceVoc );

         if ( status != BLASTER_Ok )
            {
            MV_SetErrorCode( MV_BlasterError );
            return( MV_Error );
            }

         MV_MixRate = BLASTER_GetPlaybackRate();
         break;

      case ProAudioSpectrum :
      case SoundMan16 :
         status = PAS_BeginBufferedPlayback( MV_MixBuffer[ 0 ],
            TotalBufferSize, MV_NumberOfBuffers,
            MV_RequestedMixRate, MV_MixMode, MV_ServiceVoc );

         if ( status != PAS_Ok )
            {
            MV_SetErrorCode( MV_PasError );
            return( MV_Error );
            }

         MV_MixRate = PAS_GetPlaybackRate();
         break;

      #ifndef SOUNDSOURCE_OFF
      case SoundSource :
      case TandySoundSource :
         SS_BeginBufferedPlayback( MV_MixBuffer[ 0 ],
            TotalBufferSize, MV_NumberOfBuffers,
            MV_ServiceVoc );
         MV_MixRate = SS_SampleRate;
         break;
      #endif
      }

   return( MV_Ok );
   }


/*---------------------------------------------------------------------
   Function: MV_StopPlayback

   Stops the sound playback engine.
---------------------------------------------------------------------*/

void MV_StopPlayback
   (
   void
   )

   {
   // Stop sound playback
   switch( MV_SoundCard )
      {
      case SoundBlaster :
      case Awe32 :
         BLASTER_StopPlayback();
         break;

      case ProAudioSpectrum :
      case SoundMan16 :
         PAS_StopPlayback();
         break;

      #ifndef SOUNDSOURCE_OFF
      case SoundSource :
      case TandySoundSource :
         SS_StopPlayback();
         break;
      #endif
      }
   }


/*---------------------------------------------------------------------
   Function: MV_StartRecording

   Starts the sound recording engine.
---------------------------------------------------------------------*/

int MV_StartRecording
   (
   int MixRate,
   void ( *function )( char *ptr, int length )
   )

   {
   return( MV_Ok );
   }


/*---------------------------------------------------------------------
   Function: MV_StopRecord

   Stops the sound record engine.
---------------------------------------------------------------------*/

void MV_StopRecord
   (
   void
   )

   {
   }


/*---------------------------------------------------------------------
   Function: MV_StartDemandFeedPlayback

   Plays a digitized sound from a user controlled buffering system.
---------------------------------------------------------------------*/

int MV_StartDemandFeedPlayback
   (
   int MixRate,
   int vol,
   int left,
   int right,
   void ( *function )( char **ptr, unsigned long *length )
   )

   {
   return( MV_Ok );
   }


/*---------------------------------------------------------------------
   Function: MV_GetNextVOCBlock

   Interperate the information of a VOC format sound file.
---------------------------------------------------------------------*/

void MV_GetNextVOCBlock
   (
   VoiceNode *voice
   )

   {
   unsigned char *ptr;
   int            blocktype;
   int            lastblocktype;
   unsigned long  blocklength;
   unsigned long  samplespeed;
   unsigned int   tc;
   int            packtype;
   int            voicemode;
   int            done;
   unsigned       BitsPerSample;
   unsigned       Channels;
   unsigned       Format;

   if ( voice->BlockLength > 0 )
      {
      voice->sound       += 0x8000;
      voice->position    -= 0x80000000;
      voice->length       = min( voice->BlockLength, 0x8000 );
      voice->BlockLength -= voice->length;
      voice->length     <<= 16;
      return;
      }

   ptr = ( unsigned char * )voice->NextBlock;

   voice->Playing = TRUE;

   done = FALSE;
   while( !done )
      {
      blocktype = ( int )*ptr;
      blocklength = ( *( unsigned long * )( ptr + 1 ) ) & 0x00ffffff;
      ptr += 4;

      voicemode = 0;
      lastblocktype = 0;
      switch( blocktype )
         {
         case 0 :
            // End of data
            voice->Playing = FALSE;
            done = TRUE;
            break;

         case 1 :
            // Sound data block
            if ( lastblocktype != 8 )
               {
               tc = ( unsigned int )*ptr;
               packtype = *( ptr + 1 );
               }

            ptr += 2;
            blocklength -= 2;

            samplespeed = 256000000L / ( 65536 - ( tc << 8 ) );

            // Skip packed or stereo data
            if ( ( packtype != 0 ) || ( voicemode != 0 ) )
               {
               ptr += blocklength;
               }
            else
               {
               done = TRUE;
               }
            break;

         case 2 :
            // Sound continuation block
            samplespeed = voice->SamplingRate;
            done = TRUE;
            break;

         case 3 :
            // Silence
            // Not implimented.
            ptr += blocklength;
            break;

         case 4 :
            // Marker
            // Not implimented.
            ptr += blocklength;
            break;

         case 5 :
            // ASCII string
            // Not implimented.
            ptr += blocklength;
            break;

         case 6 :
            // Repeat begin
            voice->LoopCount = *( unsigned short * )ptr;
            ptr += blocklength;
            voice->LoopStart = ptr;
            break;

         case 7 :
            // Repeat end
            ptr += blocklength;
            if ( lastblocktype == 6 )
               {
               voice->LoopCount = 0;
               }
            else
               {
               if ( ( voice->LoopCount > 0 ) && ( voice->LoopStart != NULL ) )
                  {
                  ptr = voice->LoopStart;
                  if ( voice->LoopCount < 0xffff )
                     {
                     voice->LoopCount--;
                     if ( voice->LoopCount == 0 )
                        {
                        voice->LoopStart = NULL;
                        }
                     }
                  }
               }
            break;

         case 8 :
            // Extended block
            tc = *( unsigned short * )ptr;
            packtype = *( ptr + 2 );
            voicemode = *( ptr + 3 );
            ptr += blocklength;
            break;

         case 9 :
            // New sound data block
            samplespeed = *( unsigned long * )ptr;
            BitsPerSample = ( unsigned )*( ptr + 4 );
            Channels = ( unsigned )*( ptr + 5 );
            Format = ( unsigned )*( unsigned short * )( ptr + 6 );

            if ( ( BitsPerSample == 8 ) && ( Channels == 1 ) &&
               ( Format == 0 ) )
               {
               ptr += 12;
               done = TRUE;
               }
            else
               {
               ptr += blocklength;
               }
            break;

         default :
            // Unknown data.  Probably not a VOC file.
            voice->Playing = FALSE;
            done = TRUE;
            break;
         }

      lastblocktype = blocktype;
      }

   if ( voice->Playing )
      {
      voice->NextBlock    = ptr + blocklength;
      voice->sound        = ptr;

      voice->SamplingRate = samplespeed;
      voice->RateScale   = ( voice->SamplingRate * voice->PitchScale ) / MV_MixRate;

      voice->position     = 0;
      voice->length       = min( blocklength, 0x8000 );
      voice->BlockLength  = blocklength - voice->length;
      voice->length     <<= 16;
      }
   }


/*---------------------------------------------------------------------
   Function: MV_Play

   Begin playback of sound data with the given sound levels and
   priority.
---------------------------------------------------------------------*/

int MV_Play
   (
   char *ptr,
   int   pitchoffset,
   int   vol,
   int   left,
   int   right,
   int   priority,
   unsigned long callbackval
   )

   {
   VoiceNode   *voice;
   int         buffer;
   unsigned    flags;

   if ( !MV_Installed )
      {
      MV_SetErrorCode( MV_NotInstalled );
      return( MV_Error );
      }

   // Request a voice from the voice pool
   voice = MV_AllocVoice( priority );
   if ( voice == NULL )
      {
      MV_SetErrorCode( MV_NoVoices );
      return( MV_Error );
      }

   voice->NextBlock   = ptr + *( unsigned short int * )( ptr + 0x14 );
   voice->LoopStart   = NULL;
   voice->LoopCount   = 0;
   voice->BlockLength = 0;
   voice->PitchScale  = PITCH_GetScale( pitchoffset );

   MV_GetNextVOCBlock( voice );

   voice->next = NULL;
   voice->prev = NULL;

   for( buffer = 0; buffer < NumberOfBuffers; buffer++ )
      {
      voice->Active[ buffer ] = TRUE;
      }

   voice->Volume      = MIX_VOLUME( vol );

   if ( MV_SwapLeftRight )
      {
      // SBPro uses reversed panning
      voice->LeftVolume  = MIX_VOLUME( right );
      voice->RightVolume = MIX_VOLUME( left );
      }
   else
      {
      voice->LeftVolume  = MIX_VOLUME( left );
      voice->RightVolume = MIX_VOLUME( right );
      }

   voice->priority = priority;
   voice->callbackval = callbackval;

   flags = DisableInterrupts();
   LL_AddToTail( VoiceNode, &VoiceList, voice );
   RestoreInterrupts( flags );

   MV_SetErrorCode( MV_Ok );
   return( voice->handle );
   }


/*---------------------------------------------------------------------
   Function: MV_Play3D

   Begin playback of sound data at specified angle and distance
   from listener.
---------------------------------------------------------------------*/

int MV_Play3D
   (
   char *ptr,
   int  pitchoffset,
   int  angle,
   int  distance,
   int  priority,
   unsigned long callbackval
   )

   {
   int left;
   int right;
   int mid;
   int volume;
   int status;

   if ( !MV_Installed )
      {
      MV_SetErrorCode( MV_NotInstalled );
      return( MV_Error );
      }

   if ( distance < 0 )
      {
      distance  = -distance;
      angle    += MV_NumPanPositions / 2;
      }

   volume = MIX_VOLUME( distance );

   // Ensure angle is within 0 - 31
   angle &= MV_MaxPanPosition;

   left  = MV_PanTable[ angle ][ volume ].left;
   right = MV_PanTable[ angle ][ volume ].right;
   mid   = max( 0, 255 - distance );

   status = MV_Play( ptr, pitchoffset, mid, left, right, priority,
      callbackval );

   return( status );
   }


/*---------------------------------------------------------------------
   Function: MV_LockEnd

   Used for determining the length of the functions to lock in memory.
---------------------------------------------------------------------*/

static void MV_LockEnd
   (
   void
   )

   {
   }


/*---------------------------------------------------------------------
   Function: MV_CalcVolume

   Create the table used to convert sound data to a specific volume
   level.
---------------------------------------------------------------------*/

void MV_CalcVolume
   (
   int MaxLevel
   )

   {
   int volume;
   int val;
   int level;
   int rate;
   int i;
   unsigned    flags;

   flags = DisableInterrupts();


   // For each volume level, create a translation table with the
   // appropriate volume calculated.
   rate  = ( MaxLevel << 16 ) / MV_MaxVolume;
   level = 0;

   if ( MV_MixMode & SIXTEEN_BIT )
      {
      for( volume = 0; volume <= MV_MaxVolume; volume++ )
         {
         for( i = 0; i < 256; i++ )
            {
            val = i - 128;
            val *= level;
//            val /= MV_MaxVoices;
//            val >>= 16;
            val >>= 20;
            ( *MV_16BitVolumeTable )[ volume ][ i ] = val;
            }
         level += rate;
         }
/*
//    if ( ( MV_SoundCard == ProAudioSpectrum ) ||
//       ( MV_SoundCard == SoundMan16 ) )
         {
         for( volume = 0; volume < 4 * 256 * 16; volume++ )
            {
            HarshClipTable16s[ volume ] = -0x8000;
            HarshClipTable16s[ volume + 4 * 256 * 16 ] = 0xffff - 0x8000;
            }

         for( volume = 0; volume < 16 * 256; volume++ )
            {
            HarshClipTable16s[ volume + 4 * 256 * 16 - 0x800 ] =
               volume * 16 - 0x8000;
            }
         }
      else
         {
*/
         for( volume = 0; volume < 4 * 256 * 16; volume++ )
            {
            HarshClipTable16[ volume ] = 0;
            HarshClipTable16[ volume + 4 * 256 * 16 ] = 0xffff;
            }

         for( volume = 0; volume < 16 * 256; volume++ )
            {
            HarshClipTable16[ volume + 4 * 256 * 16 - 0x800 ] = volume * 16;
            }
//         }
      }
   else
      {
      for( volume = 0; volume <= MV_MaxVolume; volume++ )
         {
         for( i = 0; i < 256; i++ )
            {
            val = i - 128;
            val *= level;
// DEBUG
//            val /= MV_MaxVoices;
            val >>= 24;
            ( *MV_8BitVolumeTable )[ volume ][ i ] = val;
            }
         level += rate;
         }
      for( volume = 0; volume < 4 * 256; volume++ )
         {
         HarshClipTable[ volume ] = 0;
         HarshClipTable[ volume + 4 * 256 ] = 255;
         }
      for( volume = 0; volume < 256; volume++ )
         {
         HarshClipTable[ volume + 4 * 256 - 128 ] = volume;
         }
      }

   RestoreInterrupts( flags );
   }


/*---------------------------------------------------------------------
   Function: MV_CalcPanTable

   Create the table used to determine the stereo volume level of
   a sound located at a specific angle and distance from the listener.
---------------------------------------------------------------------*/

void MV_CalcPanTable
   (
   void
   )

   {
   int   level;
   int   angle;
   int   distance;
   int   HalfAngle;
   int   ramp;

   HalfAngle = ( MV_NumPanPositions / 2 );

   for( distance = 0; distance <= MV_MaxVolume; distance++ )
      {
      level = ( 255 * ( MV_MaxVolume - distance ) ) / MV_MaxVolume;
      for( angle = 0; angle <= HalfAngle / 2; angle++ )
         {
         ramp = level - ( ( level * angle ) /
            ( MV_NumPanPositions / 4 ) );

         MV_PanTable[ angle ][ distance ].left = ramp;
         MV_PanTable[ HalfAngle - angle ][ distance ].left = ramp;
         MV_PanTable[ HalfAngle + angle ][ distance ].left = level;
         MV_PanTable[ MV_MaxPanPosition - angle ][ distance ].left = level;

         MV_PanTable[ angle ][ distance ].right = level;
         MV_PanTable[ HalfAngle - angle ][ distance ].right = level;
         MV_PanTable[ HalfAngle + angle ][ distance ].right = ramp;
         MV_PanTable[ MV_MaxPanPosition - angle ][ distance ].right = ramp;
         }
      }
   }


/*---------------------------------------------------------------------
   Function: MV_SetVolume

   Sets the volume of digitized sound playback.
---------------------------------------------------------------------*/

void MV_SetVolume
   (
   int volume
   )

   {
   int maxlevel;

   volume = max( 0, volume );
   volume = min( volume, MV_MaxTotalVolume );

   MV_TotalVolume = volume;

   maxlevel = ( MV_MaxLevel * volume ) / MV_MaxTotalVolume;
   //printf( "maxlevel = %d\n", maxlevel );

   // Calculate volume table
   MV_CalcVolume( maxlevel );
   }


/*---------------------------------------------------------------------
   Function: MV_GetVolume

   Returns the volume of digitized sound playback.
---------------------------------------------------------------------*/

int MV_GetVolume
   (
   void
   )

   {
   return( MV_TotalVolume );
   }


/*---------------------------------------------------------------------
   Function: MV_SetCallBack

   Set the function to call when a voice stops.
---------------------------------------------------------------------*/

void MV_SetCallBack
   (
   void ( *function )( unsigned long )
   )

   {
   MV_CallBackFunc = function;
   }


/*---------------------------------------------------------------------
   Function: MV_Init

   Perform the initialization of variables and memory used by
   Multivoc.
---------------------------------------------------------------------*/

int MV_Init
   (
   int soundcard,
   int MixRate,
   int Voices,
   int MixMode
   )

   {
   char *ptr;
   int  status;
   int  buffer;
   int  index;

   if ( MV_Installed )
      {
      MV_Shutdown();
      }

   MV_SetErrorCode( MV_Ok );

   status = MV_LockMemory();
   if ( status != MV_Ok )
      {
      return( status );
      }

   status = USRHOOKS_GetMem( &MV_Voices, Voices * sizeof( VoiceNode ) );
   if ( status != USRHOOKS_Ok )
      {
      MV_UnlockMemory();
      MV_SetErrorCode( MV_NoMem );
      return( MV_Error );
      }

   if ( MixMode & SIXTEEN_BIT )
      {
      status = USRHOOKS_GetMem( &HarshClipTable, sizeof( HARSH_CLIP_TABLE_16 ) );
      HarshClipTable16 = ( unsigned short * )HarshClipTable;
      HarshClipTable16s = ( short * )HarshClipTable;
      }
   else
      {
      status = USRHOOKS_GetMem( &HarshClipTable, sizeof( HARSH_CLIP_TABLE_8 ) );
      HarshClipTable16 = ( unsigned short * )HarshClipTable;
      HarshClipTable16s = ( short * )HarshClipTable;
      }

   if ( status != USRHOOKS_Ok )
      {
      USRHOOKS_FreeMem( MV_Voices );
      MV_SetErrorCode( MV_NoMem );
      MV_UnlockMemory();
      return( MV_Error );
      }

   // Set number of voices before calculating volume table
   MV_MaxVoices = Voices;

   VoiceList.start = NULL;
   VoiceList.end   = NULL;
   VoicePool.start = NULL;
   VoicePool.end   = NULL;

   for( index = 0; index < Voices; index++ )
      {
      LL_AddToTail( VoiceNode, &VoicePool, &MV_Voices[ index ] );
      }

   // Allocate mix buffer within 1st megabyte
   status = DPMI_GetDOSMemory( &ptr, &MV_BufferDescriptor,
      2 * TotalBufferSize );

   if ( status )
      {
      MV_SetErrorCode( MV_NoMem );
      MV_UnlockMemory();
      return( MV_Error );
      }

   MV_SwapLeftRight = FALSE;

   // Initialize the sound card
   switch( soundcard )
      {
      case SoundBlaster :
      case Awe32 :
         status = BLASTER_Init();
         if ( status != BLASTER_Ok )
            {
            MV_SetErrorCode( MV_BlasterError );
            }

         if ( ( BLASTER_Config.Type == SBPro ) ||
            ( BLASTER_Config.Type == SBPro2 ) )
            {
            MV_SwapLeftRight = TRUE;
            }
         break;

      case ProAudioSpectrum :
      case SoundMan16 :
         status = PAS_Init();
         if ( status != PAS_Ok )
            {
            MV_SetErrorCode( MV_PasError );
            }
         break;

      #ifndef SOUNDSOURCE_OFF
      case SoundSource :
      case TandySoundSource :
         status = SS_Init( soundcard );
         if ( status != SS_Ok )
            {
            MV_SetErrorCode( MV_SoundSourceError );
            }
         break;
      #endif

      default :
         MV_SetErrorCode( MV_UnsupportedCard );
         break;
      }

   if ( MV_ErrorCode != MV_Ok )
      {
      DPMI_FreeDOSMemory( MV_BufferDescriptor );
      MV_UnlockMemory();
      return( MV_Error );
      }

   MV_SoundCard = soundcard;

   MV_Installed = TRUE;

   MV_CallBackFunc = NULL;

   // Set the sampling rate
   MV_RequestedMixRate = MixRate;

   // Set Mixer to play stereo digitized sound
   MV_SetMixMode( MixMode );

   // Make sure we don't cross a physical page
   if ( ( ( unsigned long )ptr & 0xffff ) + TotalBufferSize > 0x10000 )
      {
      ptr = ( char * )( ( ( unsigned long )ptr & 0xff0000 ) + 0x10000 );
      }

   for( buffer = 0; buffer < MV_NumberOfBuffers; buffer++ )
      {
      MV_MixBuffer[ buffer ] = ptr;
      ptr += MV_BufferSize;
      }

   // Calculate pan table
   MV_CalcPanTable();

   MV_SetVolume( MV_MaxTotalVolume );

   // Init pitch scaler
   PITCH_Init();

   // Start the playback engine
   status = MV_StartPlayback();
   if ( status != MV_Ok )
      {
      // Preserve error code while we shutdown.
      status = MV_ErrorCode;
      MV_Shutdown();
      MV_SetErrorCode( status );
      return( MV_Error );
      }

   return( MV_Ok );
   }


/*---------------------------------------------------------------------
   Function: MV_Shutdown

   Restore any resources allocated by Multivoc back to the system.
---------------------------------------------------------------------*/

int MV_Shutdown
   (
   void
   )

   {
   int      buffer;
   unsigned flags;

   if ( !MV_Installed )
      {
      MV_SetErrorCode( MV_NotInstalled );
      return( MV_Error );
      }

   flags = DisableInterrupts();

   MV_KillAllVoices();

   MV_Installed = FALSE;

   // Stop the sound playback engine
   MV_StopPlayback();

   // Shutdown the sound card
   switch( MV_SoundCard )
      {
      case SoundBlaster :
      case Awe32 :
         BLASTER_Shutdown();
         break;

      case ProAudioSpectrum :
      case SoundMan16 :
         PAS_Shutdown();
         break;

      #ifndef SOUNDSOURCE_OFF
      case SoundSource :
      case TandySoundSource :
         SS_Shutdown();
         break;
      #endif
      }

   RestoreInterrupts( flags );

   // Free any voices we allocated
   USRHOOKS_FreeMem( MV_Voices );
   MV_Voices = NULL;

   VoiceList.start = NULL;
   VoiceList.end   = NULL;
   VoicePool.start = NULL;
   VoicePool.end   = NULL;

   MV_MaxVoices = 1;

   // Release the descriptor from our mix buffer
   DPMI_FreeDOSMemory( MV_BufferDescriptor );
   for( buffer = 0; buffer < NumberOfBuffers; buffer++ )
      {
      MV_MixBuffer[ buffer ] = NULL;
      }

   return( MV_Ok );
   }


/*---------------------------------------------------------------------
   Function: MV_UnlockMemory

   Unlocks all neccessary data.
---------------------------------------------------------------------*/

void MV_UnlockMemory
   (
   void
   )

   {
   PITCH_UnlockMemory();

   DPMI_UnlockMemoryRegion( MV_LockStart, MV_LockEnd );
   DPMI_Unlock( MV_VolumeTable );
   DPMI_Unlock( MV_8BitVolumeTable );
   DPMI_Unlock( MV_16BitVolumeTable );
   DPMI_Unlock( MV_PanTable );
   DPMI_Unlock( MV_Installed );
   DPMI_Unlock( MV_SoundCard );
   DPMI_Unlock( MV_TotalVolume );
   DPMI_Unlock( MV_MaxVoices );
   DPMI_Unlock( MV_BufferSize );
   DPMI_Unlock( MV_SampleSize );
   DPMI_Unlock( MV_NumberOfBuffers );
   DPMI_Unlock( MV_MixMode );
   DPMI_Unlock( MV_Silence );
   DPMI_Unlock( MV_SwapLeftRight );
   DPMI_Unlock( MV_RequestedMixRate );
   DPMI_Unlock( MV_MixRate );
   DPMI_Unlock( MV_BufferDescriptor );
   DPMI_Unlock( MV_MixBuffer );
   DPMI_Unlock( MV_Voices );
   DPMI_Unlock( VoiceList );
   DPMI_Unlock( VoicePool );
   DPMI_Unlock( MV_MixPage );
   DPMI_Unlock( MV_PlayPage );
   DPMI_Unlock( MV_VoiceHandle );
   DPMI_Unlock( MV_CallBackFunc );
   DPMI_Unlock( HarshClipTable );
   DPMI_Unlock( HarshClipTable16 );
   DPMI_Unlock( HarshClipTable16s );
   DPMI_Unlock( MV_ErrorCode );
   }


/*---------------------------------------------------------------------
   Function: MV_LockMemory

   Locks all neccessary data.
---------------------------------------------------------------------*/

int MV_LockMemory
   (
   void
   )

   {
   int status;
   int pitchstatus;

   status  = DPMI_LockMemoryRegion( MV_LockStart, MV_LockEnd );
   status |= DPMI_Lock( MV_VolumeTable );
   status |= DPMI_Lock( MV_8BitVolumeTable );
   status |= DPMI_Lock( MV_16BitVolumeTable );
   status |= DPMI_Lock( MV_PanTable );
   status |= DPMI_Lock( MV_Installed );
   status |= DPMI_Lock( MV_SoundCard );
   status |= DPMI_Lock( MV_TotalVolume );
   status |= DPMI_Lock( MV_MaxVoices );
   status |= DPMI_Lock( MV_BufferSize );
   status |= DPMI_Lock( MV_SampleSize );
   status |= DPMI_Lock( MV_NumberOfBuffers );
   status |= DPMI_Lock( MV_MixMode );
   status |= DPMI_Lock( MV_Silence );
   status |= DPMI_Lock( MV_SwapLeftRight );
   status |= DPMI_Lock( MV_RequestedMixRate );
   status |= DPMI_Lock( MV_MixRate );
   status |= DPMI_Lock( MV_BufferDescriptor );
   status |= DPMI_Lock( MV_MixBuffer );
   status |= DPMI_Lock( MV_Voices );
   status |= DPMI_Lock( VoiceList );
   status |= DPMI_Lock( VoicePool );
   status |= DPMI_Lock( MV_MixPage );
   status |= DPMI_Lock( MV_PlayPage );
   status |= DPMI_Lock( MV_VoiceHandle );
   status |= DPMI_Lock( MV_CallBackFunc );
   status |= DPMI_Lock( HarshClipTable );
   status |= DPMI_Lock( HarshClipTable16 );
   status |= DPMI_Lock( HarshClipTable16s );
   status |= DPMI_Lock( MV_ErrorCode );

   pitchstatus = PITCH_LockMemory();
   if ( ( pitchstatus != PITCH_Ok ) || ( status != DPMI_Ok ) )
      {
      MV_UnlockMemory();
      MV_SetErrorCode( MV_DPMI_Error );
      return( MV_Error );
      }

   return( MV_Ok );
   }
